#pragma once

#include <vector>

#include "TypeDef.h"

namespace FlooP
{
	class Object;
	class Scope;
	class Procedure;

	enum OpCode
	{
		OP_NOP,		// No operation
		OP_PUSH,	// Push object onto top of stack
		OP_COPY,	// Duplicates whatever is on top of the stack
		OP_POP,		// Pop object back from top of stack

		OP_ABORT,	// Small hack for aborts and quits
		OP_QUIT,

		OP_CALL,	// Call a function with name from top of the stack
		OP_RETURN,	// Return from a function call, a return value is on top of the stack

		OP_ROUTPUT,  // Pushes the value of OUTPUT onto the stack
		OP_RCELL,	// Pushes the value of CELL(x) onto the stack
		OP_SOUTPUT, // Sets the OUTPUT variable
		OP_SCELL,	// Sets the CELL(x) variable
		OP_REF,		// References a variable to the stack
		OP_SET,		// Sets a variable from the top of the stack

		OP_BR,		// Branch unconditionally
		OP_BRT,		// If top of the stack has False then branch
		OP_BRF,		// If top of the stack has True then branch

		OP_ADD,		// Add two objects from top of stack, and push the answer to the stack
		OP_MUL,		// Multiply two objects from top of stack, and push the answer to the stack

		OP_OR,		// Or two objects from top of stack, and push the answer to the stack
		OP_AND,		// And two objects from top of stack, and push the answer to the stack
		OP_EQ,		// Compare two objects from top of stack, and push the boolean answer to the stack
		OP_LT,		// Compare two objects from top of stack, and push the boolean answer to the stack
		OP_GT,		// Compare two objects from top of stack, and push the boolean answer to the stack
	};

	struct Instruction
	{
		Instruction (OpCode op)
		:
			m_op (op)
		{}

		Instruction (OpCode op, uint32_t arg)
		:
			m_op  (op),
			m_arg (arg)
		{}

		Instruction (OpCode op, std::shared_ptr<Object> object)
		:
			m_op	 (op),
			m_object (object)
		{}

		Instruction (OpCode op, std::shared_ptr<Scope> scope)
		:
			m_op	(op),
			m_scope (scope)
		{}

		Instruction (OpCode op, std::shared_ptr<Scope> scope, std::shared_ptr<Object> object)
		:
			m_op	 (op),
			m_scope  (scope),
			m_object (object)
		{}

		Instruction (OpCode op, uint32_t arg, std::shared_ptr<Object> object)
		:
			m_op	 (op),
			m_arg	 (arg),
			m_object (object)
		{}

		OpCode   m_op;
		uint32_t m_arg;

		std::shared_ptr<Scope>  m_scope;
		std::shared_ptr<Object> m_object;
	};

	class CodeBlock
	{
		public:

		CodeBlock () {}

		void write(OpCode op)
		{
			m_instructions.push_back (Instruction (op));
		}

		void write(OpCode op, uint32_t arg)
		{
			m_instructions.push_back (Instruction (op, arg));
		}

		void write(OpCode op, std::shared_ptr<Object> object)
		{
			m_instructions.push_back (Instruction (op, object));
		}

		void write(OpCode op, std::shared_ptr<Scope> scope)
		{
			m_instructions.push_back (Instruction (op, scope));
		}

		void write(OpCode op, std::shared_ptr<Scope> scope, std::shared_ptr<Object> object)
		{
			m_instructions.push_back (Instruction (op, scope, object));
		}

		void write(OpCode op, uint32_t arg, std::shared_ptr<Object> object)
		{
			m_instructions.push_back (Instruction (op, arg, object));
		}

		Instruction& operator[] (uint32_t i)
		{ 
			return m_instructions[i]; 
		}

		uint32_t count () const
		{
			return m_instructions.size ();
		}

		protected:

		friend class Procedure;

		std::vector<Instruction> m_instructions;
	};
}